/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "Camera.h"
#include "../Directors/DX11Director.h"
#include "../Managers/DX11Manager.h"

namespace Hogshead
{
	namespace DX11Rendering
	{
		ComponentCPP(Camera)

		//Default Constructor
		//	-register the game camera variable with the parameter field
		//	-set the default value of the game camera variable
		Camera::Camera(void) : _field_of_view_radians(Math::Pi/2.0f), _field_of_view_degrees(90), _aspect_ratio(16.0f/10.0f),
							_near_clip( 0.01f ), _far_clip(1000.0f), _orthographic(false), _orthographic_width(16.0f)
		{
			_pf.registerVariable(ParameterField::Bool, "game", &_game_camera);

			_pf.registerVariable(ParameterField::Float, "fieldOfView", &_field_of_view_degrees);
			_pf.registerVariable(ParameterField::Float, "aspectRatio", &_aspect_ratio);
			_pf.registerVariable(ParameterField::Float, "nearClip", &_near_clip);
			_pf.registerVariable(ParameterField::Float, "farClip", &_far_clip);
			_pf.registerVariable(ParameterField::Bool, "orthographic", &_orthographic);
			_pf.registerVariable(ParameterField::Float, "orthographicWidth", &_orthographic_width);

			_game_camera = true;
		}

		Camera::~Camera(void)
		{

		}

		void Camera::finalize()
		{
		
		}

		//Clone Method
		//	-Acquire a new camera object
		//	-Copy the view frustum and the game camera variable
		IComponent* Camera::clone() const
		{
			Camera* new_cam = Factory::acquire<Camera>();
			
			new_cam->_field_of_view_degrees = _field_of_view_degrees;
			new_cam->_field_of_view_radians = _field_of_view_radians;
			new_cam->_aspect_ratio = _aspect_ratio;
			new_cam->_near_clip = _near_clip;
			new_cam->_far_clip = _far_clip;

			new_cam->_game_camera = _game_camera;
		 	return new_cam;
		}

		Pair<Vector3, Vector3> Camera::getMouseRay(Vector2 in_mouse_coordinates)
		{
			Matrix4& transform = (this->getView() * this->getProjection()).getInverse();
		
			const Matrix4& view = this->getView();
			const Matrix4& project = this->getProjection();
		
			const Matrix4& view_inverse = view.getInverse();
			Matrix4 projection_inverse;
			project.getSlowInverse(projection_inverse);


			// convert from viewport space to screen space
			Vector2 pos = in_mouse_coordinates;

			int width = DX11Manager::getWidth();
			int height = DX11Manager::getHeight();

			pos.x /= width / 2.0f;
			pos.x -= 1.0f;
			pos.y /= -height / 2.0f;
			pos.y += 1.0f;
			
			// get near and far point 
			Vector4 near_point(pos.x, pos.y, 0, 1);
			Vector4 far_point(pos.x, pos.y, 1, 1);
			
			near_point = view_inverse * projection_inverse * near_point;
			far_point = view_inverse * projection_inverse * far_point;

			return Pair<Vector3, Vector3>(near_point.project(), far_point.project());
		}

		//Intialize method
		//	-register the camera with the DX11Director
		//	-Calculate the camera frustum
		//		NOTE: Some assumptions are made about the projection
		//		 matrix as that is created by the DX11Manager and does
		//		 not currently communicate with the camera.
		void Camera::initialize()
		{
			IDirector* potential_director;
			potential_director = this->getParentScene()->getDirector("DX11Director");
			if( potential_director )
			{
				if( _game_camera )
				{
					DX11Director* dx11_director = potential_director->as<DX11Director>();
					dx11_director->registerComponent( this );
				}

			}
			
			if(_orthographic)
				_projection = Matrix4::createOrthographicProjection( _orthographic_width, _orthographic_width / _aspect_ratio, _far_clip, _near_clip );
			else
			{
				_field_of_view_radians = Math::degreesToRadians(_field_of_view_degrees);
				_projection = Matrix4::createPerspectiveProjection( _field_of_view_radians, _aspect_ratio, _near_clip, _far_clip );
			}
			

		}

		//This method returns the position of the camera eye.
		Vector4 Camera::getPosition()
		{
			return Vector4(this->getParentGameObject()->getTransform().getWorldPosition(), 1.0f);
		}

		//This method returns the view matrix of the camera.
		const Matrix4& Camera::getView()
		{
			//write to our matrix
			Matrix4 raw_world = this->getParentGameObject()->getTransform().getWorldMatrix().getInverse();
			//get the version w/o scale
			_view.setForward( raw_world.getForward().normalize() );
			_view.setUp( raw_world.getUp().normalize() );
			_view.setRight( raw_world.getRight().normalize() );

			//get translation
			_view.setTranslation( raw_world.getTranslation() );

			return _view;
		}

		const Matrix4& Camera::getProjection()
		{
			return _projection;
		}

		//This method returns the forward vector of the camera.
		Vector3 Camera::getForward()
		{
			Matrix4 raw_world = this->getParentGameObject()->getTransform().getWorldMatrix();
			return raw_world.getForward().normalize();
		}


		//This method returns the up vector of the camera.
		Vector3 Camera::getUp()
		{
			Matrix4 raw_world = this->getParentGameObject()->getTransform().getWorldMatrix();
			return raw_world.getUp().normalize();
		}


		//This method returns the right vector of the camera
		Vector3 Camera::getRight()
		{
			Matrix4 raw_world = this->getParentGameObject()->getTransform().getWorldMatrix();
			return raw_world.getRight().normalize();
		}
	}
}